package com.ybear.ybcomponent.widget.damping.helper

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.Rect
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import kotlin.math.abs

/**
 * 滑动阻尼帮助类，用于实现下拉和上拉时的阻尼回弹效果。
 *
 * @see IDamping 必须实现的接口
 *
 * 需要实现的核心接口：
 * //用于获取子布局（优先获取首个ViewGroup，没有则获取第一个View），如果没有子布局则用传入的ViewGroup。
 * {@link DampingHelper.onFinishInflate}
 */
open class DampingHelper : IDamping {
    // 用于存储当前 View 的边界信息
    private val mDampingRect = Rect()
    // 阻尼效果作用的 View，通常是需要实现阻尼效果的 ViewGroup
    private var mDampingView: View? = null
    // 恢复动画的插值器，默认为 DecelerateInterpolator，实现减速回弹效果
    private var mRecoverInterpolator: Interpolator = DecelerateInterpolator()
    // 阻尼滑动监听器，用于监听阻尼滑动过程中的事件
    private var mOnDampingScrollListener: OnDampingScrollListener? = null
    // 恢复动画时长，单位毫秒
    private var mRecoverAnimationDuration = 0L
    // 阻尼系数，数值越高，阻尼效果越明显，即下拉或上拉的距离越大
    private var mDampingValue = 0f
    // 手指按下时的 X 或 Y 坐标，取决于滑动方向，用于计算滑动距离
    private var mDownXorY = Float.MIN_VALUE
    private var mViewCanScroll: ViewCanScrollHelper? = null

    // 是否启用阻尼效果，默认为 true
    private var isEnabledDamping = true
    // 是否启用下拉阻尼，默认为 false
    private var isEnablePullDown = false
    // 是否启用上拉阻尼，默认为 false
    private var isEnablePullUp = false
    // 是否正在处理触摸事件，用于判断是否需要拦截触摸事件
    private var isHandlingTouch = false
    // 是否是向上滚动（下拉），用于判断滑动方向
    private var isScrollPull = false
    // 手指是否移动，用于判断是否需要触发阻尼效果
    private var isMoved = false
    // 是否完成了一次下拉或上拉操作，用于判断是否需要重置状态
    private var isCompletePull = false

    // 阻尼滑动方向，默认为垂直方向
    @Orientation
    private var mOrientation: Int = Orientation.VERTICAL

    // 是否是垂直方向滑动
    private val isVertical: Boolean
        get() = mOrientation == Orientation.VERTICAL

    fun init(): DampingHelper {
        //创建控件是否可滑动帮助类
        mViewCanScroll = createViewCanScrollHelper()
        return this
    }

    /**
     * 设置阻尼滑动监听器
     * @param listener 监听器
     */
    override fun setOnDampingScrollListener(listener: OnDampingScrollListener?) {
        mOnDampingScrollListener = listener
    }

    /**
     * 设置阻尼系数
     * @param value 数值越高，阻尼效果越明显
     */
    override fun setDampingValue(value: Float) {
        mDampingValue = value
    }

    /**
     * 设置恢复动画时长
     * @param duration 时长
     */
    override fun setRecoverAnimationDuration(duration: Long) {
        mRecoverAnimationDuration = duration
    }

    /**
     * 设置恢复动画插值器
     * @param i 插值器，默认为 [DecelerateInterpolator]
     */
    override fun setRecoverInterpolator(i: Interpolator) {
        mRecoverInterpolator = i
    }

    /**
     * 设置是否启用阻尼效果
     * @param enable true 启用，false 禁用
     */
    override fun setEnabledDamping(enable: Boolean) {
        isEnabledDamping = enable
    }

    /**
     * 获取阻尼效果是否启用
     * @return true 启用，false 禁用
     */
    override fun isEnabledDamping(): Boolean {
        return isEnabledDamping
    }

    /**
     * 设置是否启用下拉阻尼
     * @param enable 是否启用
     */
    override fun setEnablePullDown(enable: Boolean) {
        isEnablePullDown = enable
    }

    /**
     * 设置是否启用上拉阻尼
     * @param enable 是否启用
     */
    override fun setEnablePullUp(enable: Boolean) {
        isEnablePullUp = enable
    }

    /**
     * 设置阻尼滑动方向
     * 水平滑动: [Orientation.HORIZONTAL]
     * 垂直滑动: [Orientation.VERTICAL]
     * @param orientation [Orientation]
     */
    override fun setDampingOrientation(orientation: Int) {
        mOrientation = orientation
        mViewCanScroll?.orientation = orientation
    }

    /**
     * 当以当前 View 为根的视图层次结构完成 inflate 操作后会被调用
     * 用于获取子布局（优先获取首个ViewGroup，没有则获取第一个View），如果没有子布局则用传入的ViewGroup
     * @param vg 当前 View 被附加到的 ViewGroup
     */
    fun onFinishInflate(vg: ViewGroup) {
        // 保存对传入的 ViewGroup 的引用
        mDampingView = vg
    }

    /**
     * 用于在布局阶段为 View 的子视图分配大小和位置，并在此时更新可以滚动的子 View 缓存
     * @param l 左边界位置
     * @param t 上边界位置
     * @param r 右边界位置
     * @param b 下边界位置
     */
    fun onLayout(l: Int, t: Int, r: Int, b: Int) {
        // 保存当前 View 的边界信息到 mDampingRect 中
        mDampingRect[l, t, r] = b
        // 更新滚动缓存
        mViewCanScroll?.updateChildScrollCache( mDampingView )
    }

    /**
     * 分发触摸事件，处理阻尼效果
     * @param damping 实现 IDamping 接口的对象
     * @param ev 触摸事件
     * @return 是否消费了触摸事件
     */
    fun dispatchTouchEvent(damping: IDamping, ev: MotionEvent): Boolean {
        // 如果未启用阻尼效果，则直接传递事件
        if (!isEnabledDamping) return damping.superDispatchTouchEvent(ev)

        // 获取当前触摸点的 X 或 Y 坐标
        val currentXorY = if (isVertical) ev.y else ev.x
        // 根据触摸事件类型进行处理
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                // 记录按下时的坐标
                mDownXorY = currentXorY
                // 初始化状态
                isHandlingTouch = false
                // 如果是 ViewGroup，则初始化 DampingView 并请求父 View 不要拦截事件
                if (damping is ViewGroup) {
                    if (mDampingView == null) onFinishInflate(damping)
                    damping.parent.requestDisallowInterceptTouchEvent(true)
                }
            }
            MotionEvent.ACTION_MOVE -> {
                // 计算手指移动的距离
                val deltaXorY = currentXorY - mDownXorY
                // 判断是否向上滚动（下拉）
                isScrollPull = deltaXorY > 0
                // 判断是否可以上拉或下拉
                val canUp = canPullUp()
                val canDown = canPullDown()
                // 检查子 View 是否可以滚动
                val canChildScroll = if (damping is ViewGroup)
                    mViewCanScroll?.canAnyChildScroll( damping, isScrollPull, ev ) ?: false
                else
                    false
                // 如果满足条件，则处理滑动事件：
                // 1. 手指向上滑动，且可以下拉，且子 View 不能滚动
                // 2. 手指向下滑动，且可以上拉，且子 View 不能滚动
                if ((deltaXorY > 0 && canDown && !canChildScroll) ||
                    (deltaXorY < 0 && canUp && !canChildScroll)
                ) {
                    isHandlingTouch = true
                    handleActionMove(damping, ev, currentXorY)
                    return true
                }
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // 如果是 ViewGroup，则请求父 View 可以拦截事件
                if (damping is ViewGroup) {
                    damping.parent.requestDisallowInterceptTouchEvent(false)
                }
                // 如果是 DampingHelper 在处理事件，则进行恢复操作
                if (isHandlingTouch) {
                    mDownXorY = Float.MIN_VALUE
                    recoverLayout(damping)
                    return true
                }
            }
        }
        // 如果不是 DampingHelper 处理的事件，则传递给子 View 处理
        return if (isHandlingTouch) {
            true // 消费掉事件
        } else {
            damping.superDispatchTouchEvent(ev)
        }
    }

    /**
     * 处理 ACTION_MOVE 事件，实现阻尼效果
     * @param damping 实现 IDamping 接口的对象
     * @param ev 触摸事件
     * @param xOrY 当前触摸点的 X 或 Y 坐标
     */
    private fun handleActionMove(damping: IDamping, ev: MotionEvent, xOrY: Float) {
        // 在触发阻尼效果之前，回调 onDampingStart() 监听器
        if (!isCompletePull && isMoved) mOnDampingScrollListener?.onStart()
        // 计算手指移动的距离
        val scrollXorYDiff = (xOrY - mDownXorY).toInt()
        // 判断是下拉还是上拉
        val pullDown = scrollXorYDiff > 0 && canPullDown()
        val pullUp = scrollXorYDiff < 0 && canPullUp()
        // 如果是下拉或上拉，则处理阻尼效果
        if (pullDown || pullUp) {
            // 取消事件传递，避免子 View 处理滑动事件
            ev.action = MotionEvent.ACTION_CANCEL
            damping.superDispatchTouchEvent(ev)
            // 计算偏移量，根据滑动距离和阻尼系数计算
            val offset = (scrollXorYDiff * mDampingValue).toInt()
            // 根据方向设置偏移量
            val layoutLR = if (isVertical) 0 else offset
            val layoutTB = if (isVertical) offset else 0
            // 更新 View 的位置，实现阻尼效果
            mDampingView?.layout(
                mDampingRect.left + layoutLR,
                mDampingRect.top + layoutTB,
                mDampingRect.right + layoutLR,
                mDampingRect.bottom + layoutTB
            )
            // 更新状态
            isMoved = true
            isCompletePull = false
            // 在阻尼效果生效时，回调 onDampingScroll() 监听器
            mOnDampingScrollListener?.onScroll(
                ev.getX(0), ev.getY(0), offset.toFloat())
        } else {
            // 更新状态
            isMoved = false
            isCompletePull = true
        }
    }

    /**
     * 将触摸事件传递给子 View 处理
     * @param ev 触摸事件
     * @return 是否消费了触摸事件
     */
    override fun superDispatchTouchEvent(ev: MotionEvent): Boolean {
        return false
    }

    /**
     * 恢复布局，使用动画将 View 恢复到初始位置
     * @param damping 实现 IDamping 接口的对象
     * @return 恢复动画
     */
    protected open fun recoverLayout(damping: IDamping): ValueAnimator {
        // 判断是下拉还是上拉
        val isPull = isScrollPull
        // 获取初始位置和目标位置
        val startPosition = if (isVertical) mDampingRect.top else mDampingRect.left
        val endPosition = getViewOffset(damping, isPull)
        // 计算动画距离
        val distance = abs(endPosition - startPosition).toFloat()
        // 创建 ValueAnimator，用于实现动画效果
        val animator = ValueAnimator.ofFloat(0f, distance).apply {
            duration = mRecoverAnimationDuration
            interpolator = mRecoverInterpolator
        }
        // 添加动画更新监听器，在动画更新时更新 View 的位置
        animator.addUpdateListener { valueAnimator ->
            // 获取动画进度
            val animatedValue = valueAnimator.animatedValue as Float
            // 计算偏移量
            val translation = if (isPull) -animatedValue else animatedValue
            // 根据方向设置偏移量
            if (isVertical) {
                mDampingView?.translationY = translation
            } else {
                mDampingView?.translationX = translation
            }
        }
        // 添加动画结束监听器，在动画结束后重置 View 的状态
        animator.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                mDampingView?.let {
                    // 重置布局参数
                    val left = if (isVertical) mDampingRect.left else endPosition
                    val top = if (isVertical) endPosition else mDampingRect.top
                    val right = if (isVertical) mDampingRect.right else endPosition + it.width
                    val bottom = if (isVertical) endPosition + it.height else mDampingRect.bottom
                    it.layout(left, top, right, bottom)
                    // 重置 translation
                    it.translationX = 0f
                    it.translationY = 0f
                }
                // 在恢复动画结束后，回调 onDampingEnd() 监听器
                mOnDampingScrollListener?.onEnd()
            }
        })
        // 启动动画
        animator.start()
        // 返回动画对象
        return animator
    }

    /**
     * 获取控件的偏移值，包括 margin 和 translation
     * @param damping 实现 IDamping 接口的对象
     * @param isPull 是否是下拉
     * @return 偏移值
     */
    protected open fun getViewOffset(damping: IDamping, isPull: Boolean): Int {
        val viewGroup = damping as? ViewGroup ?: return 0
        val lp = viewGroup.layoutParams
        var toOffset = 0
        // 计算 margin 偏移量
        if (lp is MarginLayoutParams) toOffset =
            if (isVertical)
                if (isPull) lp.topMargin else lp.bottomMargin
            else
                if (isPull) lp.marginStart else lp.marginEnd
        // 计算 translation 偏移量
        toOffset += (if (isVertical) viewGroup.translationY else viewGroup.translationX).toInt()
        return toOffset
    }

    /**
     * 重置 DampingView 的布局，将其恢复到初始位置
     */
    private fun resetDampingViewLayout() {
        mDampingView?.layout(
            mDampingRect.left, mDampingRect.top,
            mDampingRect.right, mDampingRect.bottom
        )
    }

    /**
     * 判断是否可以下拉，如果已启用下拉阻尼且 View 不能再向上滚动，则可以下拉
     *
     * @return true：可以，false:不可以
     */
    protected open fun canPullDown(): Boolean {
        if (!isEnablePullDown) return false
        return if (isVertical) mDampingView?.canScrollVertically(-1) != true
        else mDampingView?.canScrollHorizontally(-1) != true
    }

    /**
     * 判断是否可以上拉，如果已启用上拉阻尼且 View 不能再向下滚动，则可以上拉
     *
     * @return true：可以，false:不可以
     */
    protected open fun canPullUp(): Boolean {
        if (!isEnablePullUp) return false
        return if (isVertical) mDampingView?.canScrollVertically(1) != true
        else mDampingView?.canScrollHorizontally(1) != true
    }

    /**
     * 创建控件是否可滑动帮助类
     */
    protected open fun createViewCanScrollHelper() : ViewCanScrollHelper {
        val helper = ViewCanScrollHelper()
        helper.orientation = mOrientation
        return helper
    }
}